﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Scripting.Actions;
using Microsoft.Scripting.Runtime;
using Microsoft.Scripting.Generation;
using Microsoft.Scripting;
using Microsoft.Linq.Expressions;
using Microsoft.Scripting.Ast;
using System.Reflection;

namespace JavascriptEngine.Runtime
{
    public class JavascriptOperation : InvokeBinder
    {
        public JavascriptBinder Binder { get; set; }

        public JavascriptOperation(JavascriptBinder binder, int argCount, params string[] argNames)
            : base(new CallInfo(argCount, argNames))
        {
            Binder = binder;
        }

        public bool CanConvertFrom(Type fromType, Type toType, bool toNotNullable, Microsoft.Scripting.Actions.Calls.NarrowingLevel level)
        {
            if (fromType == typeof(DynamicNull)) return true;
            if (fromType == toType || toType.IsAssignableFrom(fromType)) return true;
            if (CompilerHelpers.HasImplicitConversion(fromType, toType)) return true;

            var fromCode = Type.GetTypeCode(fromType);
            var toCode = Type.GetTypeCode(toType);

            if (fromCode == TypeCode.Int32
                && (toCode == TypeCode.Int64 || toCode == TypeCode.Double || toCode == TypeCode.Decimal))
                return true;

            if (fromCode == TypeCode.Double
                && toCode == TypeCode.Decimal)
                return true;

            return (fromCode == TypeCode.String || toCode == TypeCode.String);
        }

        public Microsoft.Scripting.Actions.Calls.Candidate PreferConvert(Type t1, Type t2)
        {
            throw new NotImplementedException();
        }

        public static DynamicMetaObject GetRuntimeTypeMoFromModel(
                                      DynamicMetaObject typeModelMO)
        {
            // Get tm.ReflType
            return new DynamicMetaObject(
                    Expression.Convert(typeModelMO.Expression, typeof(JavaScriptObject)),
                typeModelMO.Restrictions.Merge(
                    BindingRestrictions.GetTypeRestriction(
                        typeModelMO.Expression, typeof(JavaScriptObject)))//,
                
            );
        }
        public static Expression[] ConvertArguments(
                         DynamicMetaObject[] args, ParameterInfo[] ps)
        {
            Expression[] callArgs = new Expression[ps.Length];
            for (int i = 0; i < ps.Length; i++)
            {
                if (i >= args.Length)
                    callArgs[i] = Expression.Constant(null, ps[i].ParameterType);
                else
                {
                    Expression argExpr = args[i].Expression;
                    if (args[i].LimitType == typeof(object) &&
                        ps[i].ParameterType == typeof(Type))
                    {
                        // Get arg.ReflType
                        argExpr = GetRuntimeTypeMoFromModel(args[i]).Expression;
                    }
                    argExpr = Expression.Convert(argExpr, ps[i].ParameterType);
                    callArgs[i] = argExpr;
                }
            }
            return callArgs;
        }

        public static Expression EnsureObjectResult(Expression expr)
        {
            if (!expr.Type.IsValueType)
                return expr;
            if (expr.Type == typeof(void))
                return Expression.Block(
                           expr, Expression.Default(typeof(JavaScriptObject)));
            else
                return Expression.Convert(expr, typeof(JavaScriptObject));
        }

        public override DynamicMetaObject FallbackInvoke(DynamicMetaObject target, DynamicMetaObject[] args, DynamicMetaObject errorSuggestion)
        {
            JavaScriptObject[] jsObjArgs = new JavaScriptObject[args.Length];
            for (int i = 0; i < args.Length; i++)
            {
                //DynamicMetaObject dynamicObject = Binder.ConvertTo(typeof(JavaScriptObject), ConversionResultKind.ExplicitCast, args[i]);
                jsObjArgs[i] = args[i].Value as JavaScriptObject;
                //args[i] = dynamicObject;

            }

            if (target.LimitType.IsAssignableFrom( typeof(JavaScriptFunction)))
            {
                JavaScriptFunction jsFunction = target.Value as JavaScriptFunction;

                //var parms = target.LimitType.GetMethod("Invoke").GetParameters();
                
                // Don't need to check if argument types match parameters.
                // If they don't, users get an argument conversion error.
                
                //var callArgs = ConvertArguments(args, parms);
                Expression[] callArgs = new Expression[jsObjArgs.Length];
                for (int i = 0; i < callArgs.Length; ++i)
                    callArgs[i] = Expression.Constant(jsObjArgs[i]);
                
                var expression = Expression.Invoke(
                    jsFunction.Function, 
                    Expression.Constant(jsObjArgs, typeof(JavaScriptObject[])));

                return new DynamicMetaObject(
                    EnsureObjectResult(expression),
                    BindingRestrictions.GetTypeRestriction(target.Expression,
                                                           target.LimitType));
            }
            return target;
        }
    }
}
